LOAD DATA.

______________________________________________________________________________

• Neocortex_v3.RData Not necessary if just plotting on already calculated PCA, UMAP, clusters.

# Full dataset:
load("../data/cfc4b2_neocortex_v3.RData")

# 40k random subset (toy dataset)
# ncx.40k <- read_rds("constellation_plots/ncx_v3_40k.rds")

# 100k random subset of excitatory neuron lineage only.
ncx.exn.100k <- read_rds("../data/exn_lineage/toy/ncx.v3.exn.sub_100k.rds")

• Cluster / marker tables.

ncx.clusters <- read_delim("../tbls/83d19_Neocortex_allindividuals_combinedclusters_v1.txt",
                              "\t", escape_double = FALSE, trim_ws = TRUE)

ncx.markers <- read_delim("../tbls/8f0fc_Neocortex_subset1_clustermarkers_combo2.txt", 
                  "\t", escape_double = FALSE, trim_ws = TRUE)

********************************************************************

1. START scrattch.hicat functions to build constellation plots.

********************************************************************

1. Define cells to build the plot from.

[All cells: 404.2K]

a. All clusters / cell types:

Keep only cells with combo2 $combined.cluster.2 annotation.

cells.cl.df <-  ncx.clusters %>% filter(str_detect(combined.cluster.2, "combo2"))

s.obj <- Neocortex %>% subsetSeurat(cells.keep = cells.cl.df$cell.name)
# 348K cells

# Sanity check
cells.cl.df$cell.name %>% identical(s.obj@meta.data %>% rownames)
[TRUE]

b. Excitatory neuron lineage only:

Keep only cells with combo2 $combined.cluster.2 annotation AND whose $combined.cluster.2 annotation belongs to excitatory neuronal lineage classes.

cells.cl.df <- ncx.clusters.exn <- 
                  ncx.clusters %>% 
                      filter(str_detect(combined.cluster.2, "combo2") ) %>% 
                        filter(str_detect(combined.cluster.2, "Neuron|CR|Dividing|RG|IPC|OPC") )
# 271.4K cells in excitatory lineage.

b.1. Cells in exn linage 100k cell toy object:

# Define which object the constellation plot is based on:
s.obj <- ncx.exn.100k

cells.cl.df <- ncx.clusters %>% 
                  filter(cell.name %in% rownames(s.obj@meta.data) )

2. Build dataframes for constellation plots.

1. cl

cl <- cells.cl.df$combined.cluster.2 %>% as.factor %>% 
          set_names(cells.cl.df$cell.name)
# 128 clusters in all _combo2_ cells
# 77 clusters in all _combo2_ & ExN lineage cells.

2. rd.dat

rd.dat <- list(umap = s.obj@reductions$umap@cell.embeddings,
               pca = s.obj@reductions$pca@cell.embeddings)

# Subset UMAP and PCA cell embeddings: keep only cells in ncx.clusters.
# (Exclude cells in cluster 0 and keep only cells with "combo2" in combined cluster 2 name.)
# rd.dat %<>% lapply(function(x) x[names(cl), ])

3. cl.df

cl.df <- get_cl_df(cl)

cl.df$clade <- str_split_fixed(cl.df$cluster_label, "_", 2)[ ,1] %>% tolower 
# Add clade_id, clade_color to cl.df
clade.cols <- data.frame(# clade = unique(cl.df$clade),
                        cluster_color = c("cr"= "darkgrey", 
                                  "dividing" = "darkkhaki", 
                                  "neuron" = "deepskyblue", 
                                  "inteneuron" = "deeppink2", 
                                  "ipc" = "brown4", 
                                  "microglia" = "darkorchid1",
                                  "opc" = "cadetblue", 
                                  "other" = "darkslateblue", 
                                  "rg" = "darkorange", 
                                  "vascular" = "blanchedalmond")
            ) %>% rownames_to_column("clade")

cl.df %<>% left_join(clade.cols)
rm(clade.cols)

# cells.cl.df: Add cluster_id column from cl.df; remove unused columns. 
cells.cl.df <- left_join(cells.cl.df %>% select(cell.name, cell.type, combined.cluster.2),
                           cl.df %>% select(cluster_label, cluster_id), 
                           by = c("combined.cluster.2" = "cluster_label")
                ) %>% mutate(cluster_id = as.factor(cluster_id))

4. rd.cl.center Find cluster centers from UMAP coordinates

rd.cl.center <- get_RD_cl_center(rd.dat$umap, cl) 

rd.cl.center %<>% 
  as.data.frame %>% 
  set_names(c("x", "y")) %>%
  add_column(cl = 1:nrow(rd.cl.center), .before = "x") %>%
  # add_column preserves rownames.
  # but moving rownames to column cluster_label anyway bc of left_join below.
  rownames_to_column("cluster_label")

5. cl.center.df

Join cl.df and rd.cl.center into cl.center.df for input into get_KNN_graph.

cl.center.df <- left_join(rd.cl.center, cl.df,
                           by = c("cluster_label")) 

6. knn.cl

# Fixes needed for proper output of knn.cl.df
# cl.center.df %<>% rename(cluster_size = "size")
# levels(cl) <- cl.df$cluster_id
cl.numeric <- cl
levels(cl.numeric) <- cl.df$cluster_id

knn.result <- RANN::nn2(data = rd.dat$pca[, 1:10], k = 15)

knn.cl <- get_knn_graph(rd.dat = rd.dat$pca[ , 1:10], 
                        cl.df =  cl.df, cl = cl.numeric, 
                        k = 15, 
                        knn.outlier.th = 2, outlier.frac.t = 0.5)

rm(rd.dat, ncx.clusters)

3. Make constellation plot

# Keep only cells where $frac [fraction of cells in cluster with nearest neighbors in different cluster] >= 0.05.
# Defined in `get_knn_graph`: 
# knn.cl.df$frac = knn.cl.df$Freq / knn.cl.df$cl.from.total
# 10% : 213 edges
knn.cl.df <- knn.cl$knn.cl.df  %>% filter(frac >= 0.1)

# Plot only edges between ExN lineage clusters.
# knn.cl.df %<>% filter_at(vars(cl.from.label, cl.to.label), 
#                        all_vars(str_detect(., "RG|IPC|Neuron|OPC|Dividing"))
#              )

cl.center.df$cluster_label %<>% str_remove("_combo2")
  
cl.plot <- plot_constellation(knn.cl.df, cl.center.df, out.dir = "../out",
                              node.label = "cluster_label", exxageration = 1, curved = TRUE, 
                              plot.parts = FALSE, plot.hull = NULL, 
                              plot.height = 40, plot.width = 40,
                              node.dodge = TRUE, label.size = 2, max_size = 20)

——————————————————————————

2. Find DE genes between connected clusters.

——————————————————————————

# Dataframe w/ the proportion of k(15) nearest neighbors in each cluster for every cell.
nn.cl.df <- knn.cl$pred.result$pred.prob %>% as.data.frame

cl.df$cluster_id %<>% as.factor()
# Possibly move to top, before making all DFs.
cells.cl.df %<>% rename(cluster_label = "combined.cluster.2") %>% 
                  mutate(cluster_label = str_remove(cluster_label, "_combo2"))

cl.df %<>% mutate(cluster_label = str_remove(cluster_label, "_combo2"))


# Add column with cells' own cluster assignment from `cells.cl.df`.
nn.cl.df %<>% left_join(cells.cl.df,
                        by = c("query" = "cell.name")
                        )
# Add cluster_label corresponding to nn.cl.

nn.cl.df %<>% left_join(cl.df %>% select(cluster_label, cluster_id),
                        by = c("nn.cl" = "cluster_id"))

nn.cl.df %<>% select(query, cluster_id_self = "cluster_id", 
                    cluster_label_self = "cluster_label.x",
                    cluster_id_nn = "nn.cl",
                    cluster_label_nn = "cluster_label.y",
                    freq = "Freq")

# nn.cl.df %<>% select(query, combined.cluster.2, cluster_id, nn.cl, )
x <- filter(knn.cl$knn.cl.df, frac >= 0.1 & cl.from != cl.to) %>% arrange(cl.from)
knn.cl$knn.cl.cl.counts %>% head
cl.df

x <- filter(nn.cl.df, cluster_label_self == "Neuron_8" & cluster_label_nn == "Neuron_3")
# 12,201 cells in Neuron_8

x %>% filter(freq > 0) %>% 
  ggplot() + geom_density(aes(freq))

Neuron_3 cells with nearest neighbors in Neuron_8

x <- cell.cl.counts %>% filter(cluster_label == "Neuron_3") %>% rename(neuron_8 = "44")

median.nnCounts <- median(x$neuron_8[x$neuron_8 > 0], na.rm = TRUE)

  ggplot(x) + 
    ggtitle("neuron_3 cells \n n/15 nearest neighbors in neuron_8") +
    
    geom_bar(aes(x = neuron_8)) +
    geom_vline(xintercept = median.nnCounts, colour = "red") +
    annotate("text", x = median.nnCounts + 1, y = 70,
             label = paste0("median = ", median.nnCounts)
  ) + 
    xlab("# of neuron_8 neighbors") +
    ylab("") +
    theme_minimal() 

NA

Compare cells above and below the median count of neuron_8 neighbors.

cells <- list()
cells$above.median <- x %>% filter(neuron_8 > median) %>% pull(cell.name)
cells$below.median <- x %>% filter(neuron_8 < median)  %>% pull(cell.name)

s.obj <- SetIdent(Neocortex, cells = cells$above.median, value = 'nn_above_med') %>% 
          SetIdent(cells = cells$below.median, value = 'nn_below_med')
# levels(s.obj)
markers <- list()
markers$nn_above_med <- FindMarkers(s.obj, slot = "scale.data", 
                                  # cells.1 = cells$above.median, cells.2 = cells$below.median,
                                  ident.1 = "nn_above_med", ident.2 = "nn_below_med", 
                                  logfc.threshold = 0)

write_tsv(markers$nn_above_med, "../out/DEgenes_neuron3_vs_neuron8.tsv")

Calculate enrichment ratio, filter genes w. adj p-value < 0.05, sort table.


markers.tmp <- markers$nn_above_med %>% rownames_to_column("gene") %>%
  mutate(enrich.ratio = pct.1 / pct.2,
         gene.score = avg_diff * enrich.ratio,
         across(.cols = where(is.numeric), .fns = round, digits = 5)
  ) %>%
  filter(p_val_adj <= 0.05) %>% 
  select(gene, pct.1, pct.2, enrich.ratio, avg_diff, gene.score, p_val_adj) %>%
  arrange(desc(gene.score))
  reactable(markers.tmp, defaultPageSize = 100,
          showSortable = TRUE, resizable = TRUE, highlight = TRUE, filterable = TRUE, minRows = 10,
          style = list(fontFamily = "Work Sans, sans-serif", fontSize = "12px")
  )
# saveWidget(markers.tmp, file = )

# Same genes in both comparisons (sanity check)
xtab_set <- function(A,B){
              both    <-  union(A,B)
              inA     <-  both %in% A
              inB     <-  both %in% B
              return(table(inA,inB))
            }
xtab_set(markers$nn_above_med$geme, markers$nn_below_med$gene)

Make heatmap:

LS0tCnRpdGxlOiAiQ29uc3RlbGxhdGlvbiBQbG90czogTmVvY29ydGV4LCAybmQgVHJpbWVzdGVyIiAKc3VidGl0bGU6ICJBbGwgU2FtcGxlcywgQWxsIGNlbGwgdHlwZXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIGdsb2JhbF9vcHRpb25zLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTgsIGZpZy5wYXRoPSdGaWdzLycsCiAgICAgICAgICAgICAgICAgICAgICBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpyZWFjdGFibGUgPC0gZnVuY3Rpb24oLi4uKSB7CiAgaHRtbHRvb2xzOjp0YWdMaXN0KHJlYWN0YWJsZTo6cmVhY3RhYmxlKC4uLikpCn0KCnNldHdkKCJ+L3Blcm1hbmVudGhlYWRkYW1hZ2VQSEQvY29uc3RlbGxhdGlvbl9wbG90cy9SLyIpCiMgYXR0YWNoKC5lbnYpCgojIHNvdXJjZSgiLi4vLi4vLi4vY29kZV9nZW5lcmFsL3NldHVwX1Jfc2Vzc2lvbl9DU0UuUiIpCnNvdXJjZV9ybWQoInNjcmF0dGNoLmhpY2F0X2Z4bnMuUm1kIikKCnNvdXJjZV9ybWQgPC0gZnVuY3Rpb24oZmlsZSwgbG9jYWwgPSBGQUxTRSwgLi4uKXsKICBvcHRpb25zKGtuaXRyLmR1cGxpY2F0ZS5sYWJlbCA9ICdhbGxvdycpCgogIHRlbXBSIDwtIHRlbXBmaWxlKHRtcGRpciA9ICIuIiwgZmlsZWV4dCA9ICIuUiIpCiAgb24uZXhpdCh1bmxpbmsodGVtcFIpKQogIGtuaXRyOjpwdXJsKGZpbGUsIG91dHB1dD10ZW1wUiwgcXVpZXQgPSBUUlVFKQoKICBlbnZpciA8LSBnbG9iYWxlbnYoKQogIHNvdXJjZSh0ZW1wUiwgbG9jYWwgPSBlbnZpciwgLi4uKQp9CmBgYAoKIyBMT0FEIERBVEEuCiMgX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fCgrigKIgTmVvY29ydGV4X3YzLlJEYXRhCk5vdCBuZWNlc3NhcnkgaWYganVzdCBwbG90dGluZyBvbiBhbHJlYWR5IGNhbGN1bGF0ZWQgUENBLCBVTUFQLCBjbHVzdGVycy4KYGBge3IgZXZhbCA9IEZBTFNFLCBlY2hvPVRSVUV9CiMgRnVsbCBkYXRhc2V0Ogpsb2FkKCIuLi9kYXRhL2NmYzRiMl9uZW9jb3J0ZXhfdjMuUkRhdGEiKQoKIyA0MGsgcmFuZG9tIHN1YnNldCAodG95IGRhdGFzZXQpCiMgbmN4LjQwayA8LSByZWFkX3JkcygiY29uc3RlbGxhdGlvbl9wbG90cy9uY3hfdjNfNDBrLnJkcyIpCgojIDEwMGsgcmFuZG9tIHN1YnNldCBvZiBleGNpdGF0b3J5IG5ldXJvbiBsaW5lYWdlIG9ubHkuCm5jeC5leG4uMTAwayA8LSByZWFkX3JkcygiLi4vZGF0YS9leG5fbGluZWFnZS90b3kvbmN4LnYzLmV4bi5zdWJfMTAway5yZHMiKQpgYGAKCuKAoiBDbHVzdGVyIC8gbWFya2VyIHRhYmxlcy4KYGBge3IgcGFnZWQucHJpbnQ9VFJVRX0KbmN4LmNsdXN0ZXJzIDwtIHJlYWRfZGVsaW0oIi4uL3RibHMvODNkMTlfTmVvY29ydGV4X2FsbGluZGl2aWR1YWxzX2NvbWJpbmVkY2x1c3RlcnNfdjEudHh0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlx0IiwgZXNjYXBlX2RvdWJsZSA9IEZBTFNFLCB0cmltX3dzID0gVFJVRSkKCm5jeC5tYXJrZXJzIDwtIHJlYWRfZGVsaW0oIi4uL3RibHMvOGYwZmNfTmVvY29ydGV4X3N1YnNldDFfY2x1c3Rlcm1hcmtlcnNfY29tYm8yLnR4dCIsIAogICAgICAgICAgICAgICAgICAiXHQiLCBlc2NhcGVfZG91YmxlID0gRkFMU0UsIHRyaW1fd3MgPSBUUlVFKQpgYGAKCiMgKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKIyAxLiBTVEFSVCBgc2NyYXR0Y2guaGljYXRgIGZ1bmN0aW9ucyB0byBidWlsZCBjb25zdGVsbGF0aW9uIHBsb3RzLgojICoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqCgojIyAxLiBEZWZpbmUgY2VsbHMgdG8gYnVpbGQgdGhlIHBsb3QgZnJvbS4KW0FsbCBjZWxsczogNDA0LjJLXQogIAojIyMgYS4gQWxsIGNsdXN0ZXJzIC8gY2VsbCB0eXBlczoKCktlZXAgb25seSBjZWxscyB3aXRoICpfY29tYm8yXyogYCRjb21iaW5lZC5jbHVzdGVyLjJgIGFubm90YXRpb24uCmBgYHtyIGV2YWw9RkFMU0V9CgpjZWxscy5jbC5kZiA8LSAgbmN4LmNsdXN0ZXJzICU+JSBmaWx0ZXIoc3RyX2RldGVjdChjb21iaW5lZC5jbHVzdGVyLjIsICJjb21ibzIiKSkKCnMub2JqIDwtIE5lb2NvcnRleCAlPiUgc3Vic2V0U2V1cmF0KGNlbGxzLmtlZXAgPSBjZWxscy5jbC5kZiRjZWxsLm5hbWUpCiMgMzQ4SyBjZWxscwoKIyBTYW5pdHkgY2hlY2sKY2VsbHMuY2wuZGYkY2VsbC5uYW1lICU+JSBpZGVudGljYWwocy5vYmpAbWV0YS5kYXRhICU+JSByb3duYW1lcykKW1RSVUVdCmBgYAoKIyMjIGIuIEV4Y2l0YXRvcnkgbmV1cm9uIGxpbmVhZ2Ugb25seToKCktlZXAgb25seSBjZWxscyB3aXRoICpfY29tYm8yXyogYCRjb21iaW5lZC5jbHVzdGVyLjJgIGFubm90YXRpb24gQU5ECndob3NlIGAkY29tYmluZWQuY2x1c3Rlci4yYCBhbm5vdGF0aW9uIGJlbG9uZ3MgdG8gZXhjaXRhdG9yeSBuZXVyb25hbCBsaW5lYWdlIGNsYXNzZXMuCmBgYHtyIGV2YWw9RkFMU0V9CmNlbGxzLmNsLmRmIDwtIG5jeC5jbHVzdGVycy5leG4gPC0gCiAgICAgICAgICAgICAgICAgIG5jeC5jbHVzdGVycyAlPiUgCiAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoc3RyX2RldGVjdChjb21iaW5lZC5jbHVzdGVyLjIsICJjb21ibzIiKSApICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHN0cl9kZXRlY3QoY29tYmluZWQuY2x1c3Rlci4yLCAiTmV1cm9ufENSfERpdmlkaW5nfFJHfElQQ3xPUEMiKSApCiMgMjcxLjRLIGNlbGxzIGluIGV4Y2l0YXRvcnkgbGluZWFnZS4KYGBgCgojIyMgYi4xLiBDZWxscyBpbiBleG4gbGluYWdlIDEwMGsgY2VsbCB0b3kgb2JqZWN0OgpgYGB7cn0KIyBEZWZpbmUgd2hpY2ggb2JqZWN0IHRoZSBjb25zdGVsbGF0aW9uIHBsb3QgaXMgYmFzZWQgb246CnMub2JqIDwtIG5jeC5leG4uMTAwawoKY2VsbHMuY2wuZGYgPC0gbmN4LmNsdXN0ZXJzICU+JSAKICAgICAgICAgICAgICAgICAgZmlsdGVyKGNlbGwubmFtZSAlaW4lIHJvd25hbWVzKHMub2JqQG1ldGEuZGF0YSkgKQpgYGAKCiMjIDIuIEJ1aWxkIGRhdGFmcmFtZXMgZm9yIGNvbnN0ZWxsYXRpb24gcGxvdHMuCgojIyMgMS4gYGNsYCAKYGBge3J9CmNsIDwtIGNlbGxzLmNsLmRmJGNvbWJpbmVkLmNsdXN0ZXIuMiAlPiUgYXMuZmFjdG9yICU+JSAKICAgICAgICAgIHNldF9uYW1lcyhjZWxscy5jbC5kZiRjZWxsLm5hbWUpCiMgMTI4IGNsdXN0ZXJzIGluIGFsbCBfY29tYm8yXyBjZWxscwojIDc3IGNsdXN0ZXJzIGluIGFsbCBfY29tYm8yXyAmIEV4TiBsaW5lYWdlIGNlbGxzLgpgYGAKCiMjIyAyLiBgcmQuZGF0YApgYGB7cn0KcmQuZGF0IDwtIGxpc3QodW1hcCA9IHMub2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MsCiAgICAgICAgICAgICAgIHBjYSA9IHMub2JqQHJlZHVjdGlvbnMkcGNhQGNlbGwuZW1iZWRkaW5ncykKCiMgU3Vic2V0IFVNQVAgYW5kIFBDQSBjZWxsIGVtYmVkZGluZ3M6IGtlZXAgb25seSBjZWxscyBpbiBuY3guY2x1c3RlcnMuCiMgKEV4Y2x1ZGUgY2VsbHMgaW4gY2x1c3RlciAwIGFuZCBrZWVwIG9ubHkgY2VsbHMgd2l0aCAiY29tYm8yIiBpbiBjb21iaW5lZCBjbHVzdGVyIDIgbmFtZS4pCiMgcmQuZGF0ICU8PiUgbGFwcGx5KGZ1bmN0aW9uKHgpIHhbbmFtZXMoY2wpLCBdKQpgYGAKCiMjIyAzLiBgY2wuZGZgCmBgYHtyfQpjbC5kZiA8LSBnZXRfY2xfZGYoY2wpCgpjbC5kZiRjbGFkZSA8LSBzdHJfc3BsaXRfZml4ZWQoY2wuZGYkY2x1c3Rlcl9sYWJlbCwgIl8iLCAyKVsgLDFdICU+JSB0b2xvd2VyIAojIEFkZCBjbGFkZV9pZCwgY2xhZGVfY29sb3IgdG8gY2wuZGYKY2xhZGUuY29scyA8LSBkYXRhLmZyYW1lKCMgY2xhZGUgPSB1bmlxdWUoY2wuZGYkY2xhZGUpLAogICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2NvbG9yID0gYygiY3IiPSAiZGFya2dyZXkiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkaXZpZGluZyIgPSAiZGFya2toYWtpIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibmV1cm9uIiA9ICJkZWVwc2t5Ymx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImludGVuZXVyb24iID0gImRlZXBwaW5rMiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImlwYyIgPSAiYnJvd240IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWljcm9nbGlhIiA9ICJkYXJrb3JjaGlkMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAib3BjIiA9ICJjYWRldGJsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJvdGhlciIgPSAiZGFya3NsYXRlYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJnIiA9ICJkYXJrb3JhbmdlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidmFzY3VsYXIiID0gImJsYW5jaGVkYWxtb25kIikKICAgICAgICAgICAgKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCJjbGFkZSIpCgpjbC5kZiAlPD4lIGxlZnRfam9pbihjbGFkZS5jb2xzKQpybShjbGFkZS5jb2xzKQoKIyBjZWxscy5jbC5kZjogQWRkIGNsdXN0ZXJfaWQgY29sdW1uIGZyb20gY2wuZGY7IHJlbW92ZSB1bnVzZWQgY29sdW1ucy4gCmNlbGxzLmNsLmRmIDwtIGxlZnRfam9pbihjZWxscy5jbC5kZiAlPiUgc2VsZWN0KGNlbGwubmFtZSwgY2VsbC50eXBlLCBjb21iaW5lZC5jbHVzdGVyLjIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjbC5kZiAlPiUgc2VsZWN0KGNsdXN0ZXJfbGFiZWwsIGNsdXN0ZXJfaWQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJjb21iaW5lZC5jbHVzdGVyLjIiID0gImNsdXN0ZXJfbGFiZWwiKQogICAgICAgICAgICAgICAgKSAlPiUgbXV0YXRlKGNsdXN0ZXJfaWQgPSBhcy5mYWN0b3IoY2x1c3Rlcl9pZCkpCmBgYAoKIyMjIDQuIGByZC5jbC5jZW50ZXJgIEZpbmQgY2x1c3RlciBjZW50ZXJzIGZyb20gVU1BUCBjb29yZGluYXRlcwpgYGB7cn0KcmQuY2wuY2VudGVyIDwtIGdldF9SRF9jbF9jZW50ZXIocmQuZGF0JHVtYXAsIGNsKSAKCnJkLmNsLmNlbnRlciAlPD4lIAogIGFzLmRhdGEuZnJhbWUgJT4lIAogIHNldF9uYW1lcyhjKCJ4IiwgInkiKSkgJT4lCiAgYWRkX2NvbHVtbihjbCA9IDE6bnJvdyhyZC5jbC5jZW50ZXIpLCAuYmVmb3JlID0gIngiKSAlPiUKICAjIGFkZF9jb2x1bW4gcHJlc2VydmVzIHJvd25hbWVzLgogICMgYnV0IG1vdmluZyByb3duYW1lcyB0byBjb2x1bW4gY2x1c3Rlcl9sYWJlbCBhbnl3YXkgYmMgb2YgbGVmdF9qb2luIGJlbG93LgogIHJvd25hbWVzX3RvX2NvbHVtbigiY2x1c3Rlcl9sYWJlbCIpCmBgYAoKIyMjIyA1LiBgY2wuY2VudGVyLmRmYApKb2luIGBjbC5kZmAgYW5kIGByZC5jbC5jZW50ZXJgIGludG8gYGNsLmNlbnRlci5kZmAgZm9yIGlucHV0IGludG8gYGdldF9LTk5fZ3JhcGhgLgpgYGB7cn0KY2wuY2VudGVyLmRmIDwtIGxlZnRfam9pbihyZC5jbC5jZW50ZXIsIGNsLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoImNsdXN0ZXJfbGFiZWwiKSkgCmBgYAoKIyMjIyA2LiBga25uLmNsYCAKYGBge3J9CiMgRml4ZXMgbmVlZGVkIGZvciBwcm9wZXIgb3V0cHV0IG9mIGtubi5jbC5kZgojIGNsLmNlbnRlci5kZiAlPD4lIHJlbmFtZShjbHVzdGVyX3NpemUgPSAic2l6ZSIpCiMgbGV2ZWxzKGNsKSA8LSBjbC5kZiRjbHVzdGVyX2lkCmNsLm51bWVyaWMgPC0gY2wKbGV2ZWxzKGNsLm51bWVyaWMpIDwtIGNsLmRmJGNsdXN0ZXJfaWQKCmtubi5yZXN1bHQgPC0gUkFOTjo6bm4yKGRhdGEgPSByZC5kYXQkcGNhWywgMToxMF0sIGsgPSAxNSkKCmtubi5jbCA8LSBnZXRfa25uX2dyYXBoKHJkLmRhdCA9IHJkLmRhdCRwY2FbICwgMToxMF0sIAogICAgICAgICAgICAgICAgICAgICAgICBjbC5kZiA9ICBjbC5kZiwgY2wgPSBjbC5udW1lcmljLCAKICAgICAgICAgICAgICAgICAgICAgICAgayA9IDE1LCAKICAgICAgICAgICAgICAgICAgICAgICAga25uLm91dGxpZXIudGggPSAyLCBvdXRsaWVyLmZyYWMudCA9IDAuNSkKCnJtKHJkLmRhdCwgbmN4LmNsdXN0ZXJzKQpgYGAKCiMjIDMuIE1ha2UgY29uc3RlbGxhdGlvbiBwbG90CmBgYHtyIGV2YWw9RkFMU0V9CiMgS2VlcCBvbmx5IGNlbGxzIHdoZXJlICRmcmFjIFtmcmFjdGlvbiBvZiBjZWxscyBpbiBjbHVzdGVyIHdpdGggbmVhcmVzdCBuZWlnaGJvcnMgaW4gZGlmZmVyZW50IGNsdXN0ZXJdID49IDAuMDUuCiMgRGVmaW5lZCBpbiBgZ2V0X2tubl9ncmFwaGA6IAojIGtubi5jbC5kZiRmcmFjID0ga25uLmNsLmRmJEZyZXEgLyBrbm4uY2wuZGYkY2wuZnJvbS50b3RhbAojIDEwJSA6IDIxMyBlZGdlcwprbm4uY2wuZGYgPC0ga25uLmNsJGtubi5jbC5kZiAgJT4lIGZpbHRlcihmcmFjID49IDAuMSkKCiMgUGxvdCBvbmx5IGVkZ2VzIGJldHdlZW4gRXhOIGxpbmVhZ2UgY2x1c3RlcnMuCiMga25uLmNsLmRmICU8PiUgZmlsdGVyX2F0KHZhcnMoY2wuZnJvbS5sYWJlbCwgY2wudG8ubGFiZWwpLCAKIyAgICAgICAgICAgICAgICAgICAgICAgIGFsbF92YXJzKHN0cl9kZXRlY3QoLiwgIlJHfElQQ3xOZXVyb258T1BDfERpdmlkaW5nIikpCiMgICAgICAgICAgICAgICkKCmNsLmNlbnRlci5kZiRjbHVzdGVyX2xhYmVsICU8PiUgc3RyX3JlbW92ZSgiX2NvbWJvMiIpCiAgCmNsLnBsb3QgPC0gcGxvdF9jb25zdGVsbGF0aW9uKGtubi5jbC5kZiwgY2wuY2VudGVyLmRmLCBvdXQuZGlyID0gIi4uL291dCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGUubGFiZWwgPSAiY2x1c3Rlcl9sYWJlbCIsIGV4eGFnZXJhdGlvbiA9IDEsIGN1cnZlZCA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90LnBhcnRzID0gRkFMU0UsIHBsb3QuaHVsbCA9IE5VTEwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90LmhlaWdodCA9IDQwLCBwbG90LndpZHRoID0gNDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGUuZG9kZ2UgPSBUUlVFLCBsYWJlbC5zaXplID0gMiwgbWF4X3NpemUgPSAyMCkKCmBgYAoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyAyLiBGaW5kIERFIGdlbmVzIGJldHdlZW4gY29ubmVjdGVkIGNsdXN0ZXJzLgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpgYGB7cn0KIyBEYXRhZnJhbWUgdy8gdGhlIHByb3BvcnRpb24gb2YgaygxNSkgbmVhcmVzdCBuZWlnaGJvcnMgaW4gZWFjaCBjbHVzdGVyIGZvciBldmVyeSBjZWxsLgpubi5jbC5kZiA8LSBrbm4uY2wkcHJlZC5yZXN1bHQkcHJlZC5wcm9iICU+JSBhcy5kYXRhLmZyYW1lCgpjbC5kZiRjbHVzdGVyX2lkICU8PiUgYXMuZmFjdG9yKCkKIyBQb3NzaWJseSBtb3ZlIHRvIHRvcCwgYmVmb3JlIG1ha2luZyBhbGwgREZzLgpjZWxscy5jbC5kZiAlPD4lIHJlbmFtZShjbHVzdGVyX2xhYmVsID0gImNvbWJpbmVkLmNsdXN0ZXIuMiIpICU+JSAKICAgICAgICAgICAgICAgICAgbXV0YXRlKGNsdXN0ZXJfbGFiZWwgPSBzdHJfcmVtb3ZlKGNsdXN0ZXJfbGFiZWwsICJfY29tYm8yIikpCgpjbC5kZiAlPD4lIG11dGF0ZShjbHVzdGVyX2xhYmVsID0gc3RyX3JlbW92ZShjbHVzdGVyX2xhYmVsLCAiX2NvbWJvMiIpKQoKCiMgQWRkIGNvbHVtbiB3aXRoIGNlbGxzJyBvd24gY2x1c3RlciBhc3NpZ25tZW50IGZyb20gYGNlbGxzLmNsLmRmYC4Kbm4uY2wuZGYgJTw+JSBsZWZ0X2pvaW4oY2VsbHMuY2wuZGYsCiAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygicXVlcnkiID0gImNlbGwubmFtZSIpCiAgICAgICAgICAgICAgICAgICAgICAgICkKIyBBZGQgY2x1c3Rlcl9sYWJlbCBjb3JyZXNwb25kaW5nIHRvIG5uLmNsLgoKbm4uY2wuZGYgJTw+JSBsZWZ0X2pvaW4oY2wuZGYgJT4lIHNlbGVjdChjbHVzdGVyX2xhYmVsLCBjbHVzdGVyX2lkKSwKICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJubi5jbCIgPSAiY2x1c3Rlcl9pZCIpKQoKbm4uY2wuZGYgJTw+JSBzZWxlY3QocXVlcnksIGNsdXN0ZXJfaWRfc2VsZiA9ICJjbHVzdGVyX2lkIiwgCiAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9sYWJlbF9zZWxmID0gImNsdXN0ZXJfbGFiZWwueCIsCiAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9pZF9ubiA9ICJubi5jbCIsCiAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9sYWJlbF9ubiA9ICJjbHVzdGVyX2xhYmVsLnkiLAogICAgICAgICAgICAgICAgICAgIGZyZXEgPSAiRnJlcSIpCgojIG5uLmNsLmRmICU8PiUgc2VsZWN0KHF1ZXJ5LCBjb21iaW5lZC5jbHVzdGVyLjIsIGNsdXN0ZXJfaWQsIG5uLmNsLCApCgpgYGAKCmBgYHtyfQp4IDwtIGZpbHRlcihrbm4uY2wka25uLmNsLmRmLCBmcmFjID49IDAuMSAmIGNsLmZyb20gIT0gY2wudG8pICU+JSBhcnJhbmdlKGNsLmZyb20pCmBgYAoKYGBge3J9Cmtubi5jbCRrbm4uY2wuY2wuY291bnRzICU+JSBoZWFkCmBgYAoKYGBge3J9CmNsLmRmCgp4IDwtIGZpbHRlcihubi5jbC5kZiwgY2x1c3Rlcl9sYWJlbF9zZWxmID09ICJOZXVyb25fOCIgJiBjbHVzdGVyX2xhYmVsX25uID09ICJOZXVyb25fMyIpCiMgMTIsMjAxIGNlbGxzIGluIE5ldXJvbl84Cgp4ICU+JSBmaWx0ZXIoZnJlcSA+IDApICU+JSAKICBnZ3Bsb3QoKSArIGdlb21fZGVuc2l0eShhZXMoZnJlcSkpCmBgYAoKTmV1cm9uXzMgY2VsbHMgd2l0aCBuZWFyZXN0IG5laWdoYm9ycyBpbiBOZXVyb25fOApgYGB7ciBlY2hvPVRSVUV9CnggPC0gY2VsbC5jbC5jb3VudHMgJT4lIGZpbHRlcihjbHVzdGVyX2xhYmVsID09ICJOZXVyb25fMyIpICU+JSByZW5hbWUobmV1cm9uXzggPSAiNDQiKQoKbWVkaWFuLm5uQ291bnRzIDwtIG1lZGlhbih4JG5ldXJvbl84W3gkbmV1cm9uXzggPiAwXSwgbmEucm0gPSBUUlVFKQoKICBnZ3Bsb3QoeCkgKyAKICAgIGdndGl0bGUoIm5ldXJvbl8zIGNlbGxzIFxuIG4vMTUgbmVhcmVzdCBuZWlnaGJvcnMgaW4gbmV1cm9uXzgiKSArCiAgICAKICAgIGdlb21fYmFyKGFlcyh4ID0gbmV1cm9uXzgpKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBtZWRpYW4ubm5Db3VudHMsIGNvbG91ciA9ICJyZWQiKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSBtZWRpYW4ubm5Db3VudHMgKyAxLCB5ID0gNzAsCiAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlMCgibWVkaWFuID0gIiwgbWVkaWFuLm5uQ291bnRzKQogICkgKyAKICAgIHhsYWIoIiMgb2YgbmV1cm9uXzggbmVpZ2hib3JzIikgKwogICAgeWxhYigiIikgKwogICAgdGhlbWVfbWluaW1hbCgpIAogICAgCmBgYAoKQ29tcGFyZSBjZWxscyBhYm92ZSBhbmQgYmVsb3cgdGhlIG1lZGlhbiBjb3VudCBvZiBuZXVyb25fOCBuZWlnaGJvcnMuCmBgYHtyIGVjaG89VFJVRX0KY2VsbHMgPC0gbGlzdCgpCmNlbGxzJGFib3ZlLm1lZGlhbiA8LSB4ICU+JSBmaWx0ZXIobmV1cm9uXzggPiBtZWRpYW4pICU+JSBwdWxsKGNlbGwubmFtZSkKY2VsbHMkYmVsb3cubWVkaWFuIDwtIHggJT4lIGZpbHRlcihuZXVyb25fOCA8IG1lZGlhbikgICU+JSBwdWxsKGNlbGwubmFtZSkKCnMub2JqIDwtIFNldElkZW50KE5lb2NvcnRleCwgY2VsbHMgPSBjZWxscyRhYm92ZS5tZWRpYW4sIHZhbHVlID0gJ25uX2Fib3ZlX21lZCcpICU+JSAKICAgICAgICAgIFNldElkZW50KGNlbGxzID0gY2VsbHMkYmVsb3cubWVkaWFuLCB2YWx1ZSA9ICdubl9iZWxvd19tZWQnKQojIGxldmVscyhzLm9iaikKbWFya2VycyA8LSBsaXN0KCkKbWFya2VycyRubl9hYm92ZV9tZWQgPC0gRmluZE1hcmtlcnMocy5vYmosIHNsb3QgPSAic2NhbGUuZGF0YSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjZWxscy4xID0gY2VsbHMkYWJvdmUubWVkaWFuLCBjZWxscy4yID0gY2VsbHMkYmVsb3cubWVkaWFuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRlbnQuMSA9ICJubl9hYm92ZV9tZWQiLCBpZGVudC4yID0gIm5uX2JlbG93X21lZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9nZmMudGhyZXNob2xkID0gMCkKCndyaXRlX3RzdihtYXJrZXJzJG5uX2Fib3ZlX21lZCwgIi4uL291dC9ERWdlbmVzX25ldXJvbjNfdnNfbmV1cm9uOC50c3YiKQpgYGAKCkNhbGN1bGF0ZSBlbnJpY2htZW50IHJhdGlvLCBmaWx0ZXIgZ2VuZXMgdy4gYWRqIHAtdmFsdWUgPCAwLjA1LCBzb3J0IHRhYmxlLgpgYGB7ciBmaWx0ZXItbWFya2VycywgZWNobz1UUlVFfQoKbWFya2Vycy50bXAgPC0gbWFya2VycyRubl9hYm92ZV9tZWQgJT4lIHJvd25hbWVzX3RvX2NvbHVtbigiZ2VuZSIpICU+JQogIG11dGF0ZShlbnJpY2gucmF0aW8gPSBwY3QuMSAvIHBjdC4yLAogICAgICAgICBnZW5lLnNjb3JlID0gYXZnX2RpZmYgKiBlbnJpY2gucmF0aW8sCiAgICAgICAgIGFjcm9zcyguY29scyA9IHdoZXJlKGlzLm51bWVyaWMpLCAuZm5zID0gcm91bmQsIGRpZ2l0cyA9IDUpCiAgKSAlPiUKICBmaWx0ZXIocF92YWxfYWRqIDw9IDAuMDUpICU+JSAKICBzZWxlY3QoZ2VuZSwgcGN0LjEsIHBjdC4yLCBlbnJpY2gucmF0aW8sIGF2Z19kaWZmLCBnZW5lLnNjb3JlLCBwX3ZhbF9hZGopICU+JQogIGFycmFuZ2UoZGVzYyhnZW5lLnNjb3JlKSkKYGBgCgpgYGB7ciByZW5kZXItdGFibGV9CiAgcmVhY3RhYmxlKG1hcmtlcnMudG1wLCBkZWZhdWx0UGFnZVNpemUgPSAxMDAsCiAgICAgICAgICBzaG93U29ydGFibGUgPSBUUlVFLCByZXNpemFibGUgPSBUUlVFLCBoaWdobGlnaHQgPSBUUlVFLCBmaWx0ZXJhYmxlID0gVFJVRSwgbWluUm93cyA9IDEwLAogICAgICAgICAgc3R5bGUgPSBsaXN0KGZvbnRGYW1pbHkgPSAiV29yayBTYW5zLCBzYW5zLXNlcmlmIiwgZm9udFNpemUgPSAiMTJweCIpCiAgKQpgYGAKCmBgYHtyfQojIHNhdmVXaWRnZXQobWFya2Vycy50bXAsIGZpbGUgPSApCgojIFNhbWUgZ2VuZXMgaW4gYm90aCBjb21wYXJpc29ucyAoc2FuaXR5IGNoZWNrKQp4dGFiX3NldCA8LSBmdW5jdGlvbihBLEIpewogICAgICAgICAgICAgIGJvdGggICAgPC0gIHVuaW9uKEEsQikKICAgICAgICAgICAgICBpbkEgICAgIDwtICBib3RoICVpbiUgQQogICAgICAgICAgICAgIGluQiAgICAgPC0gIGJvdGggJWluJSBCCiAgICAgICAgICAgICAgcmV0dXJuKHRhYmxlKGluQSxpbkIpKQogICAgICAgICAgICB9Cnh0YWJfc2V0KG1hcmtlcnMkbm5fYWJvdmVfbWVkJGdlbWUsIG1hcmtlcnMkbm5fYmVsb3dfbWVkJGdlbmUpCmBgYAoKTWFrZSBoZWF0bWFwOgpgYGB7ciBoZWF0bWFwLCBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTZ9CnRtcC5zb2JqIDwtIHN1YnNldFNldXJhdChzLm9iaiwgY2VsbHMua2VlcCA9IGZsYXR0ZW5fY2hyKGNlbGxzKSkKCm1pbih0bXAuc29iakBhc3NheXMkUk5BQHNjYWxlLmRhdGEpCgoKaGVhdG1hcCA8LSAKICBEb0hlYXRtYXAodG1wLnNvYmosIAogICAgICAgICAgIyBjZWxscyA9IGZsYXR0ZW5fY2hyKGNlbGxzKSwKICAgICAgICAgIGZlYXR1cmVzID0gbWFya2Vycy50bXAgJT4lIAogICAgICAgICAgICAjIGZpbHRlcihwY3QuMSA+IDAuMjUpICU+JSBmaWx0ZXIocGN0LjIgPCAwLjIpICU+JSAKICAgICAgICAgICAgcHVsbChnZW5lKSwKICAgICAgICAgIGRpc3AubWluID0gLTIsCiAgICAgICAgICBkaXNwLm1heCA9IDUsCiAgICAgICAgICBhbmdsZSA9IDAKICAgICAgICAgICMgc2xvdCA9ICJkYXRhIgogICAgICAgICAgIyBzbGltLmNvbC5sYWJlbCA9IFRSVUUsCiAgICAgICAgICAjIHJlbW92ZS5rZXkgPSBUUlVFCikgKwogIHRoZW1lKGwKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwKICAgICNsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNSksCiAgICAgICAgICAKICAgICAgICAjIGFzcGVjdC5yYXRpbyA9IGMoMSwyKQogICkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKGVuZCA9IDEsIG5hLnZhbHVlID0gJ3doaXRlJywgb3B0aW9uID0gIm1hZ21hIiwgZGlzY3JldGUgPSBGQUxTRSkKCmdnc2F2ZShmaWxlbmFtZSA9ICIuLi9ERWdlbmVzX25ldXJvbjNfdnNfbmV1cm9uOF9oZWF0bWFwLnBkZiIsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCwgdW5pdHMgPSAiaW4iICkKCmdncGxvdGx5KGhlYXRtYXAsIHRvb2x0aXAgPSAiRmVhdHVyZSIpCgpgYGAKCgo=